package com.wan.core.shutdown;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.lang.caller.CallerUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author wwh
 * @date 2022/6/27 10:41
 */
public class ShutdownManager {
    private static final Logger logger = LoggerFactory.getLogger(ShutdownManager.class);
    private static final Map<String,Shutdown> SHUTDOWN_MAP;
    private static final AtomicInteger AUTO_PRIORITY;

    static {
        SHUTDOWN_MAP = new ConcurrentHashMap<>();
        AUTO_PRIORITY = new AtomicInteger(0);
        Runtime.getRuntime().addShutdownHook(new Thread(ShutdownManager::shutdown));
    }

    public static void addShutdown(Shutdown shutdown){
        if(SHUTDOWN_MAP.containsKey(shutdown.getName())){
            logger.error("shutdown名称重复! name->{}",shutdown.getName());
            throw new RuntimeException("shutdown hook name repeat");
        }
        SHUTDOWN_MAP.put(shutdown.getName(),shutdown);
    }

    public static void addShutdown(String name,int priority,Runnable runnable){
        addShutdown(new Shutdown(name,priority,runnable));
    }

    public static void addShutdown(String name,Runnable runnable){
        addShutdown(name,AUTO_PRIORITY.getAndIncrement(),runnable);
    }

    public static void addShutdown(Runnable runnable){
        Class<?> callerCaller = CallerUtil.getCallerCaller();
        addShutdown(callerCaller.getName(),AUTO_PRIORITY.getAndIncrement(),runnable);
    }

    public static void addShutdown(int priority,Runnable runnable){
        Class<?> callerCaller = CallerUtil.getCallerCaller();
        addShutdown(callerCaller.getName(),priority,runnable);
    }

    public static void addLast(String name,Runnable runnable){
        addShutdown(name,Integer.MIN_VALUE,runnable);
    }

    public static void addLast(Runnable runnable){
        Class<?> callerCaller = CallerUtil.getCallerCaller();
        addLast(callerCaller.getName(),runnable);
    }

    public static void addFirst(String name,Runnable runnable){
        addShutdown(name,Integer.MAX_VALUE,runnable);
    }

    public static void addFirst(Runnable runnable){
        Class<?> callerCaller = CallerUtil.getCallerCaller();
        addFirst(callerCaller.getName(),runnable);
    }

    /**
     * 钩子的依赖前置，所以必须比依赖更先执行
     * @param depends 依赖，可多个
     */
    public static void dependOn(String name,Runnable runnable,String...depends){
        if(depends == null || depends.length == 0){
            addShutdown(name,runnable);
            return;
        }
        //找到依赖项，并取最大值
        int max = 0;
        for (String depend : depends) {
            Shutdown shutdown = SHUTDOWN_MAP.get(depend);
            if(shutdown == null){
                continue;
            }
            max = Integer.max(shutdown.getPriority(),max);
        }
        addShutdown(name,max + 1,runnable);
    }

    public static void dependOn(Runnable runnable,String...depends){
        Class<?> callerCaller = CallerUtil.getCallerCaller();
        dependOn(callerCaller.getName(),runnable,depends);
    }

    public static void dependOn(String name,Runnable runnable,Class<?>...depends){
        if(depends == null || depends.length == 0){
            addShutdown(name,runnable);
            return;
        }
        String[] strings = Arrays.stream(depends).map(Class::getName).toArray(String[]::new);
        dependOn(name,runnable,strings);
    }

    public static void dependOn(Runnable runnable,Class<?>...depends){
        Class<?> callerCaller = CallerUtil.getCallerCaller();
        dependOn(callerCaller.getName(),runnable,depends);
    }

    private static void shutdown(){
        List<Shutdown> shutdowns = new ArrayList<>(SHUTDOWN_MAP.values());
        shutdowns.sort(Comparator.comparingInt(Shutdown::getPriority).reversed());
        TimeInterval timer = DateUtil.timer();
        for (Shutdown shutdown : shutdowns) {
            try {
                timer.restart();
                shutdown.getRunnable().run();
                logger.info("shutdown hook[{}] execute over,cost:{}ms",shutdown.getName(),timer.interval());
            }catch (Throwable e){
                logger.error("shutdown hook[{}] execute error",shutdown.getName(),e);
            }
        }
    }
}
